#include <strmif.h>
#include "mkvstreamvideompeg2.hpp"
#include <climits>
//#ifdef _DEBUG
//#include "odbgstream.hpp"
//using std::endl;
//#endif

namespace MkvMux
{


MkvStreamVideoMpeg2::VideoFrame::VideoFrame(MPEGFrame* f) :
   m_pFrame(f)
{
}


MkvStreamVideoMpeg2::VideoFrame::~VideoFrame()
{
   delete m_pFrame;
}


MkvContext::VideoFrame::Type
MkvStreamVideoMpeg2::VideoFrame::type() const
{
   switch (m_pFrame->frameType)
   {
      case 'I':
      default:
         return kTypeIFrame;
         
      case 'P':
         return kTypePFrame;
         
      case 'B':
         return kTypeBFrame;
   }
}
         

ULONG MkvStreamVideoMpeg2::VideoFrame::size() const
{
   return m_pFrame->size;
}

   
const BYTE* MkvStreamVideoMpeg2::VideoFrame::data() const
{
   return m_pFrame->data;
}


ULONG MkvStreamVideoMpeg2::VideoFrame::curr_timecode_ms() const
{
   return m_pFrame->timecode_ms;
}


SHORT MkvStreamVideoMpeg2::VideoFrame::prev_timecode_ms() const
{
   const int result = m_pFrame->firstRef_ms - m_pFrame->timecode_ms;
   assert(result <= 0);
   assert(result >= SHRT_MIN);
   
   return static_cast<SHORT>(result);
}


SHORT MkvStreamVideoMpeg2::VideoFrame::next_timecode_ms() const
{
   const int result = m_pFrame->secondRef_ms - m_pFrame->timecode_ms;
   assert(result >= 0);
   assert(result <= SHRT_MAX);
   
   return static_cast<SHORT>(result);
}


MkvStreamVideoMpeg2::MkvStreamVideoMpeg2(
    MkvContext& c,
    const BYTE* p,
    ULONG n) :
    MkvStreamVideo(c),
    m_hdr(p, p + n)
{
    assert(p);
    assert(n >= 4);
}


void MkvStreamVideoMpeg2::WriteTrackName()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID2(0x536E);   //name
    f.Write1UTF8(L"MPEG-2 video stream");
}



void MkvStreamVideoMpeg2::WriteTrackCodecID()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID1(0x86);  //Codec ID
    f.Write1String("V_MPEG2");
}


void MkvStreamVideoMpeg2::WriteTrackCodecPrivate()
{
    EbmlIO::File& f = m_context.m_file;

    assert(!m_hdr.empty());
    
    const BYTE* const ha = &m_hdr[0];    

    const bytes_t::size_type hn_ = m_hdr.size();
    const ULONG hn = static_cast<ULONG>(hn_);
    
    f.WriteID2(0x63A2);  //Codec Private
    f.Write4UInt(hn);
    f.Write(ha, hn);
}
    

void MkvStreamVideoMpeg2::WriteTrackCodecName()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID3(0x258688);  //Codec Name
    f.Write1UTF8(L"MPEG-2");
}


void MkvStreamVideoMpeg2::WriteTrackSettings()
{
    EbmlIO::File& f = m_context.m_file;

    f.WriteID1(0xE0);  //video settings
    
    //allocate 2 bytes of storage for size of settings
    const __int64 begin_pos = f.SetPosition(2, STREAM_SEEK_CUR);
    
    assert(!m_hdr.empty());    
    const BYTE* const ha = &m_hdr[0];    

    const std::vector<BYTE>::size_type hn_ = m_hdr.size();
    const ULONG hn = static_cast<ULONG>(hn_);

    binary* const data = new binary[hn];
    memcpy(data, ha, hn);
    
    MPEGChunk chunk(data, hn);  //deallocates data in dtor
    
    MPEG2SequenceHeader h;    
    ParseSequenceHeader(&chunk, h);

    assert(h.width <= USHRT_MAX);
    const USHORT width = static_cast<USHORT>(h.width);
    
    assert(h.height <= USHRT_MAX);
    const USHORT height = static_cast<USHORT>(h.height);

    f.WriteID1(0xB0);  //width
    f.Write1UInt(2);
    f.Serialize2UInt(width);
    
    f.WriteID1(0xBA);  //height
    f.Write1UInt(2);
    f.Serialize2UInt(height);
    
    //TODO: the BMIH tells us whether the stride is different from width;
    //is that the pixel crop values here?

    //TODO:    
    //mkvmerge sets to Interlaced flag for the video track to 0.
    //Is this value determined from h.progressiveSequence?
    
    //aspect ratio = 4:3
    //720:480 = 1.5 = 3:2 = 4.5:3
    //720 x factor / 480 = 4:3
    //factor = (4/3) * (480/720) = 
    
    f.WriteID3(0x2383E3);  //frame rate
    f.Write1UInt(4);
    f.Serialize4Float(h.frameRate);    

    const __int64 end_pos = f.GetPosition();
    
    const __int64 size_ = end_pos - begin_pos;
    assert(size_ <= USHRT_MAX);
    
    const USHORT size = static_cast<USHORT>(size_);

    f.SetPosition(begin_pos - 2);
    f.Write2UInt(size);
    
    f.SetPosition(end_pos);
}


HRESULT MkvStreamVideoMpeg2::Write(IMediaSample* pSample)
{
   assert(pSample);

   BYTE* ptr;

   const HRESULT hr = pSample->GetPointer(&ptr);
   assert(SUCCEEDED(hr));
   assert(ptr);
   hr;
         
   const long len = pSample->GetActualDataLength();
   assert(len >= 0);

   m_parser.WriteData(ptr, len);

   while (MPEGFrame* f = m_parser.ReadFrame())
      WriteFrame(f);
     
   return S_OK;
}


int MkvStreamVideoMpeg2::EndOfStream()
{
   m_parser.SetEOS();

   while (MPEGFrame* f = m_parser.ReadFrame())
      WriteFrame(f);
     
   return m_context.EOSVideo(this);
}


#if 0 //def _DEBUG
static std::ostream& operator<<(std::ostream& os, const MPEGFrame* f)
{
    assert(f);
    
    os << "(" << f->frameType << ") tc[ms]=" << f->timecode_ms;
    
    if (f->frameType != 'I')
        os << " back[ms]=" << f->firstRef_ms;
        
    if (f->frameType == 'B')
        os << " fwd[ms]=" << f->secondRef_ms;
        
    //os << " dur[ms]=" << f->duration_ms;
            
    return os;
}
#endif


void MkvStreamVideoMpeg2::WriteFrame(MPEGFrame* f)
{
   assert(f);
   assert(f->timecode_ms >= 0);

//#ifdef _DEBUG
//   odbgstream os;
//   os << "Mpeg2::WriteFrame: " << f << endl;
//#endif
   
   VideoFrame* const pFrame = new (std::nothrow) VideoFrame(f);
   assert(pFrame);  //TODO
   
   m_context.WriteVideo(this, pFrame);
}


}  //end namespace MkvMux

